package cn.demoncat.util.mall.ehsy;


import cn.demoncat.util.exception.BizRuntimeException;
import cn.demoncat.util.exception.RestRuntimeException;
import cn.demoncat.util.http.HttpUtil;
import cn.demoncat.util.http.entity.HttpRequest;
import cn.demoncat.util.http.entity.HttpResponse;
import cn.demoncat.util.lang.*;
import cn.demoncat.util.lang.constant.StatusConstant;
import cn.demoncat.util.lang.entity.ListStr;
import cn.demoncat.util.lang.entity.MapSo;
import cn.demoncat.util.mall.ehsy.config.EhsyProperties;
import cn.demoncat.util.mall.ehsy.constant.EhsyApi;
import cn.demoncat.util.mall.ehsy.constant.EhsyConstant;
import cn.demoncat.util.mall.ehsy.constant.EhsyLogs;
import cn.demoncat.util.mall.ehsy.constant.EhsyRedisConstant;
import cn.demoncat.util.mall.ehsy.entity.*;
import cn.demoncat.util.redis.util.RedisUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * 西域对接工具
 *
 * @author 延晓磊
 * @since 2022年07月25日
 */
@Component
public class EhsyUtil {

    @Resource
    private EhsyProperties ehsyProperties;
    @Resource
    private RedisUtil redisUtil;

    private static final Logger LOG = LoggerFactory.getLogger(EhsyUtil.class);


    /**
     * 初始化商品
     *
     * 注意：西域服务端（getSkuByPage接口）有并发限制（默认6），所以初始化数据时要注意线程数
     *
     * @param consumer  商品处理：如查询、保存，fn(商品池编号)
     *
     * @author 延晓磊
     * @since 2022年08月23日
     */
    public void initSku(Consumer<String> consumer){
        // 查询商品池
        List<EhsyGetPageNumResult.Body> pools = this.getPageNum();
        Iterator<EhsyGetPageNumResult.Body> iterator = pools.iterator();
        // 查询池内商品 > 执行商品处理
        ThreadUtil.countDownLatch("EhsyInitExecutor", ehsyProperties.getPoolSize(), pools.size(), () -> consumer.accept(iterator.next().getPage_num()));
    }

    /**
     * 获取商品池编号（西域一级分类/产线；对接双方划定的商品池列表）
     *
     * @return [{page_num,name}]
     * 
     * @author 延晓磊
     * @since 2022年07月25日
     */
    public List<EhsyGetPageNumResult.Body> getPageNum(){
        return request(EhsyApi.getPageNum, new EhsyParam(), EhsyGetPageNumResult.class).getResult();
    }

    /**
     * 获取池内商品编号（对接双方划定的商品池内的SKU编码）
     *
     * 注意：部分页没有数据，应以页数作为判断条件
     *
     * @param pageNum   商品池编号
     * @param pageNo    页码，每页最多1000条
     *
     * @return {pageCount,skuIds}
     *
     * @author 延晓磊
     * @since 2022年07月25日
     */
    public EhsyGetSkuByPageResult.Body getSkuByPage(String pageNum, Integer pageNo){
        return request(EhsyApi.getSkuByPage, MapSo.getInstance("pageNum", pageNum, "pageNo", pageNo), EhsyGetSkuByPageResult.class).getResult();
    }

    /**
     * 获取商品详细信息
     *
     * @param sku   商品编号
     *
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月25日
     */
    public EhsyGetDetailResult.Body getDetail(String sku){
        return request(EhsyApi.getDetail, MapSo.getInstance("sku", sku), EhsyGetDetailResult.class).getResult();
    }

    /**
     * 获取商品上下架状态
     *
     * @param skus   商品编号
     *
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月25日
     */
    public List<EhsySkuStateResult.Body> skuState(List<String> skus){
        return request(EhsyApi.skuState, MapSo.getInstance("sku", skus), EhsySkuStateResult.class).getResult();
    }

    /**
     * 获取商品上下架状态
     *
     * @param sku   商品编号
     *
     * @return 商品状态：1上架，0下架
     *
     * @author 延晓磊
     * @since 2022年07月25日
     */
    public String skuState(String sku){
        List<EhsySkuStateResult.Body> list = skuState(ListStr.getInstance(sku));
        if (CollectionUtils.isEmpty(list)) {
            return StatusConstant.Common.NO;
        }else{
            return list.get(0).getState();
        }
    }

    /**
     * 获取商品图片信息
     *
     * @param skus   商品编号
     *
     * @return null没有图片
     *
     * @author 延晓磊
     * @since 2022年07月25日
     */
    public List<EhsySkuImageResult.Body> skuImage(List<String> skus){
        return OptionalUtil.get(request(EhsyApi.skuImage, MapSo.getInstance("sku", skus), EhsySkuImageResult.class), EhsySkuImageResult::getResult);
    }

    /**
     * 获取商品图片信息
     *
     * @param sku   商品编号
     *
     * @return null没有图片
     *
     * @author 延晓磊
     * @since 2022年07月25日
     */
    public EhsySkuImageResult.Body skuImage(String sku){
        return CollectionUtil.get(skuImage(ListStr.getInstance(sku)), 0);
    }

    /**
     * 查询商品区域购买限制
     *
     * @param skus   商品编号
     * @param areaId  地区编码（市级）
     *
     * @return [受限的商品编号]
     *
     * @author 延晓磊
     * @since 2022年07月25日
     */
    public List<String> checkAreaLimit(List<String> skus, String areaId){
        return request(EhsyApi.checkAreaLimit, MapSo.getInstance("skuIds", skus, "city", AreaUtil.getCity(areaId)), EhsyCheckAreaLimitResult.class).getResult().parallelStream().filter(EhsyCheckAreaLimitResult.Body::getIsAreaRestrict).map(EhsyCheckAreaLimitResult.Body::getSkuId).collect(Collectors.toList());
    }

    /**
     * 查询商品区域购买限制
     *
     * @param sku   商品编号
     * @param areaId  地区编码（市级）
     *
     * @return true受限
     *
     * @author 延晓磊
     * @since 2022年07月25日
     */
    public boolean checkAreaLimit(String sku, String areaId){
        return !checkAreaLimit(ListStr.getInstance(sku), areaId).isEmpty();
    }

    /**
     * 查询商品价格
     *
     * 注：不在商品池中的SKU不返回价格
     * 注：价格为null时为暂无报价
     *
     * @param skus   商品编号
     *
     * @return null没有价格（非协议商品）
     *
     * @author 延晓磊
     * @since 2022年07月25日
     */
    public List<EhsyGetSellPriceResult.Body> getSellPrice(List<String> skus){
        return OptionalUtil.get(request(EhsyApi.getSellPrice, MapSo.getInstance("sku", skus), EhsyGetSellPriceResult.class), EhsyGetSellPriceResult::getResult);
    }

    /**
     * 查询商品价格
     *
     * 注：不在商品池中的SKU不返回价格
     * 注：价格为null时为暂无报价
     *
     * @param sku   商品编号
     *
     * @return null没有价格（非协议商品）
     *
     * @author 延晓磊
     * @since 2022年07月25日
     */
    public EhsyGetSellPriceResult.Body getSellPrice(String sku){
        return CollectionUtil.get(getSellPrice(ListStr.getInstance(sku)), 0);
    }

    /**
     * 查询商品库存
     *
     * 注：西域的状态，实际只有“有货”和“预定”，无货的都会当作“预定”状态，在下单时不会校验，因此不用考虑
     *
     * @param skus   {商品编号:购买数量}
     *
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月25日
     */
    public List<EhsyGetNewStockByIdResult.Body> getNewStockById(Map<String,Integer> skus){
        return request(EhsyApi.getNewStockById, MapSo.getInstance("skuNums", skus.entrySet().stream().map(entry -> MapSo.getInstance("skuId", entry.getKey(), "num", entry.getValue())).collect(Collectors.toList())), EhsyGetNewStockByIdResult.class).getResult();
    }

    /**
     * 查询商品库存
     *
     * 注：西域的状态，实际只有“有货”和“预定”，无货的都会当作“预定”状态，在下单时不会校验，因此不用考虑
     *
     * @param sku   商品编号
     * @param num   购买数量
     *
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月25日
     */
    public EhsyGetNewStockByIdResult.Body getNewStockById(String sku, Integer num){
        return CollectionUtil.get(getNewStockById(new HashMap<String,Integer>(){{
            put(sku, num);
        }}), 0);
    }

    /**
     * 通过末级产线ID获取商品编码
     *
     * @param endCategoryId   末级产线ID（我方叶子类目，西域对接时关联）
     * @param pageNo    页码，每页最多1000条
     *
     * @return {pageCount,skuIds}
     *
     * @author 延晓磊
     * @since 2022年07月25日
     */
    public EhsyGetSkuByPageResult.Body getSkuByEndCategory(String endCategoryId, Integer pageNo){
        return request(EhsyApi.getSkuByEndCategory, MapSo.getInstance("endCategoryId", endCategoryId, "pageNo", pageNo), EhsyGetSkuByPageResult.class).getResult();
    }

    /**
     * 查询运费
     *
     * 注：通常会签约为包邮，所以本接口无需调用
     *
     * @param skus   {商品编号:数量}
     * @param totalAmount  商品总金额（商务协议要求运费单独计算的客户必传，默认传null）
     * @param province  省ID
     * @param city  市ID
     * @param county  县ID
     *
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月25日
     */
    public EhsyGetFreightResult.Body getFreight(Map<String,Integer> skus, BigDecimal totalAmount, String province, String city, String county){
        return request(EhsyApi.getFreight, MapSo.getInstance("sku", skus.entrySet().stream().map(entry -> MapSo.getInstance("skuId", entry.getKey(), "num", entry.getValue())).collect(Collectors.toList()),
                "totalAmount", totalAmount, "province", province, "city", city, "county", county, "town", 0),
                EhsyGetFreightResult.class).getResult();
    }

    /**
     * 查询运费
     *
     * 注：通常会签约为包邮，所以本接口无需调用
     *
     * @param skus          {商品编号:数量}
     * @param totalAmount   商品总金额（商务协议要求运费单独计算的客户必传，默认传null）
     * @param areaId        地区编码
     *
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月25日
     */
    public EhsyGetFreightResult.Body getFreight(Map<String,Integer> skus, BigDecimal totalAmount, String areaId){
        return getFreight(skus, totalAmount, AreaUtil.getProvince(areaId), AreaUtil.getCity(areaId), areaId);
    }

    /**
     * 统一下单（预订单）
     *
     * 1、将需要购买的商品传给西域，进行下单校验
     * 2、如果订单号相同，返回订单号重复，不能重复下单
     * 3、下单时按照下单价格模式要求做价格校验，价格不一致不能下单，以免后续订单确认失败
     * 4、如果是分子公司分别与西域开票结算，则下单的时候必须传“下单公司”明确订单归属，后续该笔订单开票申请时也必须传一样的公司名称
     * 5、由于目前都采用集中开票模式，统一下单接口中的发票信息只做参考使用，实际的开票信息按照发票申请接口确定
     * 6、下单结果返回订单中单个商品明细价税分离信息，包括裸价、税率、税额和总价，并满足税额=裸价*税率、总价=裸价+税额的关系
     *
     * @param param
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public EhsySubmitOrderResult.Body submitOrder(EhsySubmitOrderParam param){
        return request(EhsyApi.submitOrder, param, EhsySubmitOrderResult.class).getResult();
    }

    /**
     * 统一下单（预订单）
     *
     * @param param
     * @param areaId    地区编码，映射省市县
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public EhsySubmitOrderResult.Body submitOrder(EhsySubmitOrderParam param, String areaId){
        param.setProvince(Integer.valueOf(AreaUtil.getProvince(areaId)));
        param.setCity(Integer.valueOf(AreaUtil.getCity(areaId)));
        param.setCounty(Integer.valueOf(areaId));
        return submitOrder(param);
    }

    /**
     * 确认订单
     *
     * 1、确认预订单，西域开始执行订单配货
     *
     * @param orderId   西域订单号
     * @param logisticalRemark   物流备注（100字，NULL）
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public void confirmOrder(String orderId, String logisticalRemark){
        request(EhsyApi.confirmOrder, MapSo.getInstance("orderId", orderId, "logisticalRemark", logisticalRemark), EhsyResult.class);
    }

    /**
     * 确认订单
     *
     * 1、确认预订单，西域开始执行订单配货
     *
     * @param orderId   西域订单号
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public void confirmOrder(String orderId){
        confirmOrder(orderId, null);
    }

    /**
     * 取消订单
     *
     * 1、取消预订单（确认前）
     *
     * @param orderId   西域订单号
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public void cancel(String orderId){
        request(EhsyApi.cancel, MapSo.getInstance("orderId", orderId), EhsyResult.class);
    }

    /**
     * 检查订单
     *
     * 1、如果下单请求超时，通过本接口查询是否下单成功
     *
     * @param thirdOrder   我方订单号
     *
     * @throws BizRuntimeException 下单失败
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public void selectOrderIdByThirdOrder(String thirdOrder) {
        request(EhsyApi.selectOrderIdByThirdOrder, MapSo.getInstance("thirdOrder", thirdOrder), EhsyResult.class);
    }

    /**
     * 查询订单信息
     *
     * @param orderId   西域订单号
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public EhsyQrySubOrderResult.Body qrySubOrder(String orderId){
        return request(EhsyApi.qrySubOrder, MapSo.getInstance("orderId", orderId), EhsyQrySubOrderResult.class).getResult();
    }

    /**
     * 查询发货单信息
     *
     * @param packageId   包裹号
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public EhsyQryPackageResult.Body qryPackage(String packageId){
        return OptionalUtil.get(request(EhsyApi.qryPackage, MapSo.getInstance("packageId", packageId), EhsyQryPackageResult.class), EhsyQryPackageResult::getResult);
    }

    /**
     * 查询物流信息
     *
     * @param sendNo   运单号
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public EhsyLogisticsResult.Body logistics(String sendNo){
        return OptionalUtil.get(request(EhsyApi.logistics, MapSo.getInstance("sendNo", sendNo), EhsyLogisticsResult.class), EhsyLogisticsResult::getResult);
    }

    /**
     * 查询订单信息（根据第三方订单号）
     *
     * @param thirdOrder   我方订单号
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public EhsyQrySubOrderResult.Body qrySubOrderByThirdOrder(String thirdOrder){
        return request(EhsyApi.qrySubOrderByThirdOrder, MapSo.getInstance("thirdOrder", thirdOrder), EhsyQrySubOrderResult.class).getResult();
    }

    /**
     * 签收订单
     *
     * 场景：默认以西域物流的签收状态为对账基准；可以通过本方法调用，来实现以我方订单签收状态为对账依据。
     *
     * @param orderId   西域订单号
     *
     * @throws BizRuntimeException 未签收
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public void orderConfirAndReceipt(String orderId){
        request(EhsyApi.orderConfirAndReceipt, MapSo.getInstance("orderId", orderId), EhsyResult.class);
    }

    /**
     * 新建订单对账
     *
     * 1、查询该日期【新建】的订单总数及每条订单的金额信息，用于双方对账
     *
     * @param date  日期：yyyy-mm-dd
     * @param page  页码
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public EhsyCheckNewOrderResult.Body checkNewOrder(String date, String page){
        return request(EhsyApi.checkNewOrder, MapSo.getInstance("date", date, "page", page), EhsyCheckNewOrderResult.class).getResult();
    }

    /**
     * 妥投/部分妥投订单对账
     *
     * 1、查询该日期【完成妥投】的订单总数及每条订单的金额信息，用于双方对账
     * 2、针对多个包裹的订单，如订单内的包裹有部分妥投、部分拒收，则订单为部分妥投状态
     * 3、妥投和部分妥投的订单完结后参与订单的结算和开票，部分妥投的结算扣除拒收的包裹中的商品
     *
     * @param date  日期：yyyy-mm-dd
     * @param page  页码
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public EhsyCheckNewOrderResult.Body checkDlokOrder(String date, String page){
        return request(EhsyApi.checkDlokOrder, MapSo.getInstance("date", date, "page", page), EhsyCheckNewOrderResult.class).getResult();
    }

    /**
     * 拒收订单对账
     *
     * 1、查询该日期【拒收】的订单总数及每条订单的金额信息，用于双方对账
     * 2、针对有发货单的订单，只有所有包裹都拒收的订单才是拒收状态
     * 3、拒收的订单不参与结算和开票
     *
     * @param date  日期：yyyy-mm-dd
     * @param page  页码
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public EhsyCheckNewOrderResult.Body checkRefuseOrder(String date, String page){
        return request(EhsyApi.checkRefuseOrder, MapSo.getInstance("date", date, "page", page), EhsyCheckNewOrderResult.class).getResult();
    }

    /**
     * 申请售后服务
     *
     * 1、用户申请售后服务，填写退货详情，将申请发送给西域商城
     * 2、对于定制化商品等不退不换的商品，由于存在质量原因退货情况，西域商城审核后处理。如非质量问题，则做退货失败处理。
     *
     * @param param
     *
     * @return 售后服务单号
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public String serviceOrder(EhsyServiceOrderParam param){
        return request(EhsyApi.serviceOrder, param, EhsyMapResult.class).getResult().getString("serviceId");
    }

    /**
     * 查询售后服务信息
     *
     * @param serviceId  售后服务单号
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public EhsyGetServiceDetailInfoResult.Body getServiceDetailInfo(String serviceId){
        return request(EhsyApi.getServiceDetailInfo, MapSo.getInstance("serviceId", serviceId), EhsyGetServiceDetailInfoResult.class).getResult();
    }

    /**
     * 获取推送消息
     *
     * 1、西域商城的商品、价格、订单等信息有变化时，将变化的skuId、orderId及变化类型写入消息表，由对接方定时获取
     * 2、默认每次获取100条数据
     * 3、消息在双方确定和配置好产线后开始推送
     *
     * @param type      消息类型：2商品价格变更、4商品上下架变更、5订单已妥投/拒收、6添加、删除商品、7发票状态变更、8发票物流变更、9西域确认订单、10西域取消订单、16商品介绍及规格参数变更、101拆分发货单、102售后服务单完成
     * @param returnClass
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public <T extends EhsyGetResult<?>>T get(Integer type, Class<T> returnClass){
        return request(EhsyApi.get, MapSo.getInstance("type", type), returnClass);
    }

    /**
     * 获取推送消息 - 商品价格变更
     *
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public List<EhsyGet2Result.Body<EhsyGet2Result.O>> getWithSkuPrice(){
        return get(2, EhsyGet2Result.class).getResult();
    }

    /**
     * 获取推送消息 - 商品上下架变更
     *
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public List<EhsyGet2Result.Body<EhsyGet2Result.O>> getWithSkuStatus(){
        return get(4, EhsyGet2Result.class).getResult();
    }

    /**
     * 获取推送消息 - 订单已妥投/拒收
     *
     * 1、对于多包裹发货的订单，需要在所有包裹都妥投或者拒收后，西域商城将该订单的状态写入消息池
     * 2、订单状态有有妥投、拒收、部分妥投（部分拒收）
     *
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public List<EhsyGet5Result.Body<EhsyGet5Result.O>> getWithDeliveryStatus(){
        return get(5, EhsyGet5Result.class).getResult();
    }

    /**
     * 获取推送消息 - 添加、删除商品
     *
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public List<EhsyGet6Result.Body<EhsyGet6Result.O>> getWithSkuAd(){
        return get(6, EhsyGet6Result.class).getResult();
    }

    /**
     * 获取推送消息 - 发票变更
     *
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public List<EhsyGet7Result.Body<EhsyGet7Result.O>> getWithInvoice(){
        return get(7, EhsyGet7Result.class).getResult();
    }

    /**
     * 获取推送消息 - 发票物流变更
     *
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public List<EhsyGet8Result.Body<EhsyGet8Result.O>> getWithInvoiceLogistics(){
        return get(8, EhsyGet8Result.class).getResult();
    }

    /**
     * 获取推送消息 - 西域确认订单
     *
     * 1、客户确认订单后，西域根据下单、库存等实际情况对订单进行“确认”操作，“确认”后将进行锁库动作
     *
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public List<EhsyGet9Result.Body<EhsyGet9Result.O>> getWithOrderConfirm(){
        return get(9, EhsyGet9Result.class).getResult();
    }

    /**
     * 获取推送消息 - 西域取消订单
     *
     * 1、客户确认订单后，西域根据下单、库存等实际情况对订单进行“取消”操作
     *
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public List<EhsyGet10Result.Body<EhsyGet10Result.O>> getWithOrderCancel(){
        return get(10, EhsyGet10Result.class).getResult();
    }

    /**
     * 获取推送消息 - 商品介绍、规格参数、库存状态等信息变更
     *
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public List<EhsyGet2Result.Body<EhsyGet2Result.O>> getWithSkuUpdate(){
        return get(16, EhsyGet2Result.class).getResult();
    }

    /**
     * 获取推送消息 - 拆分发货单
     *
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public List<EhsyGet101Result.Body<EhsyGet101Result.O>> getWithOrderPackage(){
        return get(101, EhsyGet101Result.class).getResult();
    }

    /**
     * 获取推送消息 - 售后服务单完成
     *
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public List<EhsyGet102Result.Body<EhsyGet102Result.O>> getWithServiceFinish(){
        return get(102, EhsyGet102Result.class).getResult();
    }

    /**
     * 删除推送消息
     *
     * 1、获取消息并处理成功后，需要主动将推送消息删除
     * 
     * @param id    推送消息id
     * 
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public void delete(String id){
        request(EhsyApi.delete, MapSo.getInstance("id", id), EhsyResult.class);
    }

    /**
     * 申请开票
     *
     * 1、对于已经完结的订单，可以按照整单申请开具发票
     * 2、对于多个包裹的订单，需要订单中所有包裹都到货，且订单完结后，才可以申请开票。此类订单开票也按照整单开票，不按照包裹开票
     * 3、开票时只需要传订单信息，对于拒收的包裹中商品和退货的商品，西域会剔除，不开具发票
     *
     * @param param
     * @throws BizRuntimeException 开票失败
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public void invoiceApply(EhsyInvoiceApplyParam param){
        request(EhsyApi.invoiceApply, param, EhsyResult.class);
    }

    /**
     * 查询发票信息
     *
     * @param markId 开票申请ID
     * @return 开票信息（0004）
     * @throws BizRuntimeException 未开票：code = 3301待审核、3302驳回、3303通过待开票
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public List<EhsyQryInvoiceResult.Body> qryInvoice(String markId){
        return request(EhsyApi.qryInvoice, MapSo.getInstance("markId",markId), EhsyQryInvoiceResult.class).getResult();
    }

    /**
     * 查询发票运单
     *
     * 1、收到发票寄出的消息后，可调用此接口查询发票运单号（一个申请单可能对应多个发票运单号）
     *
     * @param markId 开票申请ID
     * @return
     * @throws BizRuntimeException 查询错误：code = 3305 运单不存在
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public List<EhsyInvoiceWayBillResult.Body> invoiceWayBill(String markId){
        return request(EhsyApi.invoiceWayBill, MapSo.getInstance("markId",markId), EhsyInvoiceWayBillResult.class).getResult();
    }

    /**
     * 查询发票详情
     *
     * @param invoiceId 发票号码
     * @param invoiceCode 发票代码
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月26日
     */
    public EhsyQryInvoiceItemResult.Body qryInvoiceItem(String invoiceId, String invoiceCode){
        return request(EhsyApi.qryInvoiceItem, MapSo.getInstance("invoiceId",invoiceId,"invoiceCode",invoiceCode), EhsyQryInvoiceItemResult.class).getResult();
    }




    /**
     * 获取Token
     *
     * @return token
     *
     * @author 延晓磊
     * @since 2022年07月25日
     */
    private String getToken(){
        // 查询缓存 > 发起申请
        return StringUtil.toDefault(redisUtil.get(EhsyRedisConstant.TOKEN.getKey()), () -> applyToken(null));
    }

    /**
     * 申请Token
     *
     * @param oldToken 旧Token：未变更时重新获取，已变更时直接返回
     *
     * @return token
     *
     * @author 延晓磊
     * @since 2022年07月25日
     */
    private String applyToken(String oldToken){
        // 加锁
        return redisUtil.getLock().tryLock(EhsyRedisConstant.LOCK_TOKEN, () ->{
            // 查询缓存
            String token = redisUtil.get(EhsyRedisConstant.TOKEN.getKey());
            if (StringUtils.isNotBlank(token) && (StringUtils.isBlank(oldToken) || !token.equals(oldToken))) {
                // 缓存有Token && (旧Token为空 || 新Token变更) > 返回
                return token;
            }
            // 没有新Token || 旧Token未变更 > 发起请求
            EhsyAccessTokenResult result = baseRequest(EhsyApi.accessToken, MapSo.getInstance(
                    "grant_type", "access_token",
                    "client_id", ehsyProperties.getClientId(),
                    "client_secret", ehsyProperties.getClientSecret(),
                    "timestamp", LocalDateUtil.toStr(),
                    "username", ehsyProperties.getUsername(),
                    "password", ehsyProperties.getPassword()
            ), null, EhsyAccessTokenResult.class);
            // 加入缓存
            redisUtil.setEx(EhsyRedisConstant.TOKEN, result.getResult().getAccess_token());
            return result.getResult().getAccess_token();
        }, () -> {
            // 未获取锁 > 等待
            ThreadUtil.sleepMillis(100);
            // 查询缓存
            String token = redisUtil.get(EhsyRedisConstant.TOKEN.getKey());
            if (StringUtils.isBlank(token) || token.equals(oldToken)) {
                // 没有Token || Token未变更 > 去申请
                return applyToken(null);
            }else{
                // Token变更
                return token;
            }
        });
    }

    /**
     * 发起请求（业务）
     *
     * @param api
     * @param param
     * @param resultClass
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月25日
     */
    private <T extends EhsyResult>T request(EhsyApi api, EhsyParam param, Class<T> resultClass){
        // 添加Token
        String token = getToken();
        param.setToken(token);
        param.setTimestamp(LocalDateUtil.toStr());
        // 发起请求
        return baseRequest(api, param, token, resultClass);
    }

    /**
     * 发起请求（业务）
     *
     * @param api
     * @param param
     * @param resultClass
     * @return
     *
     * @author 延晓磊
     * @since 2022年07月25日
     */
    private <T extends EhsyResult>T request(EhsyApi api, MapSo param, Class<T> resultClass){
        // 添加Token
        String token = getToken();
        param.put("token", token);
        param.put("timestamp", LocalDateUtil.toStr());
        // 发起请求
        return baseRequest(api, param, token, resultClass);
    }

    /**
     * 发起请求（Token、业务）
     *
     * @param api
     * @param param 请求参数
     * @param token 请求Token，用于重新获取Token时防并发
     * @param resultClass
     * @return null查询为空
     *
     * @throws RestRuntimeException 请求失败
     * @throws BizRuntimeException  业务错误：success = false
     *
     * @author 延晓磊
     * @since 2022年07月25日
     */
    private <T extends EhsyResult>T baseRequest(EhsyApi api, Object param, String token, Class<T> resultClass){
        // 参数处理
        String json = JsonUtil.toStr(param);
        // 发起请求
        LOG.info(EhsyLogs.REQUEST, api.getName(), api.name());
        LOG.debug(EhsyLogs.REQUEST_PARAM, api.getName(), json);
        T result;
        try {
            HttpResponse response = HttpUtil.request(HttpRequest.post(ehsyProperties.getUrl() + api.getUrl()).json(json));
            if (response.getStatus() == 401) {
                // Token失效，重试请求
                LOG.warn(EhsyLogs.REQUEST_RETRY, api.getName());
                token = applyToken(token);
                if (param instanceof EhsyParam) {
                    // 变更Token
                    EhsyParam ep = (EhsyParam)param;
                    ep.setToken(token);
                    return baseRequest(api, ep, token, resultClass);
                }else{
                    MapSo ms = (MapSo)param;
                    ms.put("token", token);
                    return baseRequest(api, ms, token, resultClass);
                }
            }else{
                // 接收响应
                json = response.asString();
                LOG.debug(EhsyLogs.REQUEST_RESULT, api.getName(), json);
                result = JsonUtil.parse(json, resultClass);
            }
        }catch (Exception e){
            // 请求失败
            LOG.error(EhsyLogs.REQUEST_FAIL, api.getName(), api.name(), e.getMessage(), e);
            throw RestRuntimeException.apiError("请求失败", e);
        }
        // 处理响应
        if (result.isSuccess()) {
            return result;
        }else{
            // 业务状态
            switch (result.getResultCode()) {
                // 查询数据为空
                case EhsyConstant.ReturnCode.DATA_EMPTY:
                // 价格不存在（非协议商品）
                case EhsyConstant.ReturnCode.PRICE_EMPTY:
                // 价格获取失败（非协议商品）
                case EhsyConstant.ReturnCode.PRICE_ERROR:
                    return null;
                default:
                    break;
            }
            // 其它状态
            LOG.error(EhsyLogs.REQUEST_ERROR, api.getName(), api.name(), result.getResultCode(), result.getResultMessage());
            throw new BizRuntimeException(result.getResultMessage(), result.getResultCode());
        }
    }
}
